#pragma once

#include "../errors.h"
#include "../../../sqlite/sqlite3.h"
#include <optional>
#include <variant>

namespace CQLIB {
	namespace CQSQL::utility {
		template<typename ...Options>
		struct VariantFirstNullable {
			using type = void;
		};
		template<typename T, typename ...Options>
		struct VariantFirstNullable<T, Options...> {
			using type = typename VariantFirstNullable<Options...>::type;
		};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
		template<typename T, typename ...Options>
		struct VariantFirstNullable<std::optional<T>, Options...> {
			using type = std::optional<T>;
		};
#endif
		template<typename T, typename ...Options>
		struct VariantFirstNullable<std::unique_ptr<T>, Options...> {
			using type = std::unique_ptr<T>;
		};
		template<typename ...Options>
		struct VariantFirstNullable<std::nullptr_t, Options...> {
			using type = std::nullptr_t;
		};
		template<typename Callback, typename ...Options>
		inline void variant_select_null(Callback&& callback) {
			if constexpr (std::is_same_v<typename VariantFirstNullable<Options...>::type, void>) {
				throw errors::mismatch("NULL is unsupported by this variant.", "", SQLITE_MISMATCH);
			}
			else {
				std::forward<Callback>(callback)(typename VariantFirstNullable<Options...>::type());
			}
		}

		template<typename ...Options>
		struct VariantFirstIntegerable {
			using type = void;
		};
		template<typename T, typename ...Options>
		struct VariantFirstIntegerable<T, Options...> {
			using type = typename VariantFirstIntegerable<Options...>::type;
		};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
		template<typename T, typename ...Options>
		struct VariantFirstIntegerable<std::optional<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstIntegerable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstIntegerable<Options...>::type>;
		};
#endif
		template<typename T, typename ...Options>
		struct VariantFirstIntegerable<std::enable_if_t<std::is_same_v<typename VariantFirstIntegerable<T, Options...>::type, T>>, std::unique_ptr<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstIntegerable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstIntegerable<Options...>::type>;
		};
		template<typename ...Options>
		struct VariantFirstIntegerable<int, Options...> {
			using type = int;
		};
		template<typename ...Options>
		struct VariantFirstIntegerable<sqlite_int64, Options...> {
			using type = sqlite_int64;
		};
		template<typename Callback, typename ...Options>
		inline auto variant_select_integer(Callback&& callback) {
			if constexpr (std::is_same_v<typename VariantFirstIntegerable<Options...>::type, void>) {
				throw errors::mismatch("Integer is unsupported by this variant.", "", SQLITE_MISMATCH);
			}
			else {
				std::forward<Callback>(callback)(typename VariantFirstIntegerable<Options...>::type());
			}
		}

		template<typename ...Options>
		struct VariantFirstFloatable {
			using type = void;
		};
		template<typename T, typename ...Options>
		struct VariantFirstFloatable<T, Options...> {
			using type = typename VariantFirstFloatable<Options...>::type;
		};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
		template<typename T, typename ...Options>
		struct VariantFirstFloatable<std::optional<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstFloatable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstFloatable<Options...>::type>;
		};
#endif
		template<typename T, typename ...Options>
		struct VariantFirstFloatable<std::unique_ptr<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstFloatable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstFloatable<Options...>::type>;
		};
		template<typename ...Options>
		struct VariantFirstFloatable<float, Options...> {
			using type = float;
		};
		template<typename ...Options>
		struct VariantFirstFloatable<double, Options...> {
			using type = double;
		};
		template<typename Callback, typename ...Options>
		inline auto variant_select_float(Callback&& callback) {
			if constexpr (std::is_same_v<typename VariantFirstFloatable<Options...>::type, void>) {
				throw errors::mismatch("Real is unsupported by this variant.", "", SQLITE_MISMATCH);
			}
			else {
				std::forward<Callback>(callback)(typename VariantFirstFloatable<Options...>::type());
			}
		}

		template<typename ...Options>
		struct VariantFirstTextable {
			using type = void;
		};
		template<typename T, typename ...Options>
		struct VariantFirstTextable<T, Options...> {
			using type = typename VariantFirstTextable<void, Options...>::type;
		};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
		template<typename T, typename ...Options>
		struct VariantFirstTextable<std::optional<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstTextable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstTextable<Options...>::type>;
		};
#endif
		template<typename T, typename ...Options>
		struct VariantFirstTextable<std::unique_ptr<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstTextable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstTextable<Options...>::type>;
		};
		template<typename ...Options>
		struct VariantFirstTextable<std::string, Options...> {
			using type = std::string;
		};
		template<typename ...Options>
		struct VariantFirstTextable<std::u16string, Options...> {
			using type = std::u16string;
		};
		template<typename Callback, typename ...Options>
		inline void variant_select_text(Callback&& callback) {
			if constexpr (std::is_same_v<typename VariantFirstTextable<Options...>::type, void>) {
				throw errors::mismatch("Text is unsupported by this variant.", "", SQLITE_MISMATCH);
			}
			else {
				std::forward<Callback>(callback)(typename VariantFirstTextable<Options...>::type());
			}
		}

		template<typename ...Options>
		struct VariantFirstBlobable {
			using type = void;
		};
		template<typename T, typename ...Options>
		struct VariantFirstBlobable<T, Options...> {
			using type = typename VariantFirstBlobable<Options...>::type;
		};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
		template<typename T, typename ...Options>
		struct VariantFirstBlobable<std::optional<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstBlobable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstBlobable<Options...>::type>;
		};
#endif
		template<typename T, typename ...Options>
		struct VariantFirstBlobable<std::unique_ptr<T>, Options...> {
			using type = std::conditional_t<std::is_same_v<typename VariantFirstBlobable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstBlobable<Options...>::type>;
		};
		template<typename T, typename A, typename ...Options>
		struct VariantFirstBlobable<std::enable_if_t<std::is_pod_v<T>>, std::vector<T, A>, Options...> {
			using type = std::vector<T, A>;
		};
		template<typename Callback, typename ...Options>
		inline auto variant_select_blob(Callback&& callback) {
			if constexpr (std::is_same_v<typename VariantFirstBlobable<Options...>::type, void>) {
				throw errors::mismatch("Blob is unsupported by this variant.", "", SQLITE_MISMATCH);
			}
			else {
				std::forward<Callback>(callback)(typename VariantFirstBlobable<Options...>::type());
			}
		}

		template<typename ...Options>
		inline auto variant_select(int type) {
			return [type](auto&& callback) {
				using Callback = decltype(callback);
				switch (type) {
				case SQLITE_NULL:
					variant_select_null<Callback, Options...>(std::forward<Callback>(callback));
					break;
				case SQLITE_INTEGER:
					variant_select_integer<Callback, Options...>(std::forward<Callback>(callback));
					break;
				case SQLITE_FLOAT:
					variant_select_float<Callback, Options...>(std::forward<Callback>(callback));
					break;
				case SQLITE_TEXT:
					variant_select_text<Callback, Options...>(std::forward<Callback>(callback));
					break;
				case SQLITE_BLOB:
					variant_select_blob<Callback, Options...>(std::forward<Callback>(callback));
					break;
				default:;
					/* assert(false); */
				}
			};
		}
	}
}